要用 LINQ ,一定要知道什麼是委派。要了解委派到應用委派,則一定要從基礎模式學起,才會融會貫通!本篇將說明 .Net 委派的最基本模式:具名委派。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
委派(deletate)其實也是類別,只是它是一種特殊的類別。
多特殊?委派類別在宣告的同時,就必須要定義好此委派類別中【唯一】的方法簽章(方法回傳值和方法參數的組合)。是的,最特殊的地方就是每個委派類別只有一個方法,不能自訂其他額外的成員,不像一般類別要多少成員屬性、方法、欄位,都可以自由定義。委派類別還有一個特性,它是屬於「密封」(sealed)類別,也就是無法被繼承。
委派的用途是什麼?我個人最喜歡的解釋是,委派可以讓我們把一個流程中某個部分之邏輯,留給呼叫者決定,例如:我建立一個訂單匯出Excel的功能,但是匯出那些欄位,由呼叫者決定。另外我也會這樣解釋:我只宣告有一件事情要做,但是這件事情誰做、怎麼做,由呼叫端和邏輯端處理。換言之,其實整個委派應該要切成宣告端、邏輯端、呼叫端三個面向來看:
宣告端:定義委派結構。
邏輯端:設定執行細節。
呼叫端:負責建立宣告和邏輯連接關係,並引動(Invoke)委派以執行邏輯端所設定之執行細節。
特別要注意一個限制:要和委派建立關係的方法,其方法簽章必須和委派宣告時的方法簽章相符,否則編譯會出錯。
PS. 這個限制有彈性,但正常情況下不會用到,有興趣的人可以閱讀 MSDN:http://msdn.microsoft.com/zh-tw/library/ms173174(v=vs.90).aspx
以下我設計一個情境,讓大家更容易了解委派的概念。
我是一間豪宅的新到任管家(女僕比較吸引人,但是我不想反串咩),主人交代我房子裡不可以有蟲出沒,看到蟲就火掉我。身為專業管家,我當然要先準備好蟲出沒的應變措施:當我發現房子裡有蟲出沒,我就要找除蟲高手來殺蟲,這樣就搞定啦。翻譯為程式碼如下:
//具名委派
//宣告端 - 豪宅主人交代房子裡不能有蟲,不然就火掉我,所以我要先準備好蟲出沒的應變措施,也就是要除蟲:
public delegate string 除蟲(string 蟲);
public class 豪宅 {
public void 蟲出沒(除蟲 人){
Console.WriteLine(人("蟑螂"));
}
}
//邏輯端 - 執行業務的人
public class 除蟲高手 {
public string 噴藥(string 蟲) {
return "噴殺蟲劑讓 " + 蟲 + " 掛點。\n";
}
}
//呼叫端 - 這是管家
void Main() {
豪宅 白宮 = new 豪宅();
除蟲高手 高手 = new 除蟲高手();
除蟲 除蟲的人 = new 除蟲(高手.噴藥);
白宮.蟲出沒(除蟲的人);
}
//輸出:噴殺蟲劑讓 蟑螂 掛點。
透過情境來對照程式碼,應該就清楚多了吧。宣告端定義一個除蟲的委派,然後也定義了什麼時間點(蟲出沒)要使用這個委派,邏輯端則定義了除蟲的實際執行方法,但兩者並無關係,所以最後需要由呼叫端將兩邊串連起來,並在正確的時間點,也就是蟲出沒的時候,引動這個委派作業。
接下來繼續說故事囉,因為豪宅中窗明几淨,無蟲也無塵,相信主人一定非常滿意,但是主人外出遊玩回來,一定會肚子餓,所以我必須在主人肚子餓時叫人備餐,而且主人愛喝湯,所以除了主食一定要有湯品:
//具名委派
//宣告端 - 幫主人備餐
public delegate void 備餐();
public class 豪宅主人 {
public void 肚子餓(備餐 人){
人();
}
}
//邏輯端 - 執行業務的人
public class 廚師{
public void 烤牛排(){
Console.WriteLine("牛排烤好了");
}
public void 煮雞湯(){
Console.WriteLine("雞湯煮好了");
}
public void 炒飯(){
Console.WriteLine("蛋炒飯完成");
}
}
//呼叫端 - 這是管家
void Main() {
豪宅主人 主人 = new 豪宅主人();
廚師 阿達師 = new 廚師();
備餐 主食 = new 備餐(阿達師.烤牛排);
備餐 湯品 = new 備餐(阿達師.煮雞湯);
備餐 主食加湯品 = 主食 + 湯品;
主人.肚子餓(主食加湯品);
}
/* 輸出:
雞湯煮好了
牛排烤好了
*/
這裡和前面有些不同囉,因為主人要吃主食和湯品,所以「備餐」委派在程式中被 new 了兩次,然後再用另一個「備餐」委派把它們串連起來。這個方式的專有名詞叫:多點傳送(multicasting),也就是委派一經叫用時,可以呼叫多個方法,只要使用加法或加法指派運算子 ('+' 或 '+=') 加入兩個委派即可。所以上述第三個委派也可以省掉改以主食委派用 「+=」運算子 把湯品委派串連起來:
void Main() {
豪宅主人 主人 = new 豪宅主人();
廚師 阿達師 = new 廚師();
備餐 主食 = new 備餐(阿達師.烤牛排);
備餐 湯品 = new 備餐(阿達師.煮雞湯);
//備餐 主食加湯品 = 主食 + 湯品; <--被註解囉
主食 += 湯品; //改用主食委派 += 串接湯品委派
主人.肚子餓(主食);
}
那多點傳送委派時,委派的方法調用順序為何?答案是依加入的順序,依序引動。
那怎麼移除已經加入多點傳送委派的方法呢?答案是使用遞減或遞減指派運算子('-' 或 '-=')即可:
void Main() {
豪宅主人 主人 = new 豪宅主人();
廚師 阿達師 = new 廚師();
備餐 主食 = new 備餐(阿達師.烤牛排);
備餐 湯品 = new 備餐(阿達師.煮雞湯);
備餐 飯= new 備餐(阿達師.炒飯);
//備餐 主食加湯品 = 主食 + 湯品; <--被註解囉
主食 += 湯品; //改用主食委派 += 串接湯品委派
主食 += 飯;
主食 -= 湯品; //用 –= 運算子把湯品移出多點傳送委派的方法清單中
主人.肚子餓(主食);
}
/* 輸出:
牛排烤好了
蛋炒飯完成
*/
PS. 多點傳送委派是繼承自 MulticastDelegate,其為 System.Delegate 的子類別,所以我們不用特別宣告為 MulticastDelegate 即可使用。
以上是 .Net 1.0/1.1 時,使用委派的寫法,也是委派最基本的模式,後續 .Net 2.0 的匿名委派、.Net 3.5 的通用委派、Lambda 運算式,都是從這個基本模式出發、簡化而成,所以本篇文章請朋友一定要先了解,閱讀後續文章才不會很卡。